Skip to content

feat: publish multi-arch Docker image on release#254

Merged
socksy merged 1 commit intodevelopfrom
feature/add-dockerfile
Apr 22, 2026
Merged

feat: publish multi-arch Docker image on release#254
socksy merged 1 commit intodevelopfrom
feature/add-dockerfile

Conversation

@socksy
Copy link
Copy Markdown
Contributor

@socksy socksy commented Apr 17, 2026

Adds a Dockerfile and a publish-docker reusable workflow wired into the cargo-dist release pipeline. On each tagged release it pulls the prebuilt musl binaries and pushes a distroless-based image to ghcr.io/tower/tower-cli for linux/amd64 and linux/arm64.

Also adds aarch64-unknown-linux-musl to cargo-dist targets (the wheel build already uses it, so no new toolchain requirements) and drops the vestigial announce job, which had no steps.

Summary by CodeRabbit

  • New Features
    • Docker image publishing is now enabled for releases, providing multi-architecture container image support for amd64 and arm64 platforms.
    • Container images are automatically built and published to the container registry with appropriate version tags.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d048fa2-fcc0-46e5-8b32-da15471815fe

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/add-dockerfile

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
Dockerfile (1)

10-13: Add artifact integrity verification before extraction.

Lines 10-13 download and unpack a remote archive without validating a checksum/signature. Prefer verifying the release checksum before tar -xJf.

Suggested hardening
 && curl -fsSL -o /tmp/tower.tar.xz \
       "https://github.com/tower/tower-cli/releases/download/v${VERSION}/tower-${ARCH}-unknown-linux-musl.tar.xz" \
+&& curl -fsSL -o /tmp/tower.tar.xz.sha256 \
+      "https://github.com/tower/tower-cli/releases/download/v${VERSION}/tower-${ARCH}-unknown-linux-musl.tar.xz.sha256" \
+&& sha256sum -c /tmp/tower.tar.xz.sha256 \
 && mkdir -p /out \
 && tar -xJf /tmp/tower.tar.xz -C /out --strip-components=1 \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 10 - 13, Add integrity verification for the
downloaded artifact before extraction: after downloading /tmp/tower.tar.xz (the
curl step that uses VERSION and ARCH) fetch the corresponding checksum or
signature (from the release assets or a .sha256/.asc URL), verify it (e.g.,
compare computed sha256sum of /tmp/tower.tar.xz against the fetched checksum or
import and verify a GPG signature), and fail the build if verification fails;
only then run tar -xJf /tmp/tower.tar.xz -C /out --strip-components=1. Ensure
the verification step is atomic in the Dockerfile so that invalid artifacts
never get extracted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/publish-docker.yml:
- Around line 35-43: The build strips a leading "v" into VERSION but the
Dockerfile expects the original tag (v-prefixed or not) when constructing the
release download URL, causing mismatches; stop relying on stripped VERSION for
the download and pass the original TAG through to the Docker build. Modify the
workflow to set VERSION="${TAG#v}" as before for image tagging but also pass the
unmodified TAG as a separate build-arg (e.g., RELEASE_TAG or FULL_TAG) in the
docker buildx build invocation (alongside --build-arg VERSION="$VERSION"), and
update the Dockerfile to use that RELEASE_TAG when forming the /download/... URL
(instead of v${VERSION}) so the exact release tag format is preserved.

In `@Dockerfile`:
- Around line 16-18: The final image stage uses
gcr.io/distroless/static-debian12 and sets ENTRYPOINT to /usr/local/bin/tower
but does not set a non-root user; update the final stage of the Dockerfile to
add a USER directive (e.g., a non-root UID/GID like 1000 or a created user) so
the container does not run as root, and ensure /usr/local/bin/tower and any
required directories have appropriate ownership/permissions for that UID before
switching users (adjust COPY/permission steps as needed).

---

Nitpick comments:
In `@Dockerfile`:
- Around line 10-13: Add integrity verification for the downloaded artifact
before extraction: after downloading /tmp/tower.tar.xz (the curl step that uses
VERSION and ARCH) fetch the corresponding checksum or signature (from the
release assets or a .sha256/.asc URL), verify it (e.g., compare computed
sha256sum of /tmp/tower.tar.xz against the fetched checksum or import and verify
a GPG signature), and fail the build if verification fails; only then run tar
-xJf /tmp/tower.tar.xz -C /out --strip-components=1. Ensure the verification
step is atomic in the Dockerfile so that invalid artifacts never get extracted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4a8d39b-d207-41ee-9946-41be0fe7c466

📥 Commits

Reviewing files that changed from the base of the PR and between 92d3e84 and 575fdd5.

📒 Files selected for processing (4)
  • .github/workflows/publish-docker.yml
  • .github/workflows/release.yml
  • Dockerfile
  • dist-workspace.toml

Comment on lines +35 to +43
VERSION="${TAG#v}"
TAGS=(--tag "ghcr.io/tower/tower-cli:$VERSION")
if [ "$PRERELEASE" != "true" ]; then
TAGS+=(--tag "ghcr.io/tower/tower-cli:latest")
fi
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg VERSION="$VERSION" \
"${TAGS[@]}" \
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In cargo-dist release manifests, is announcement_tagguaranteed to include avprefix, or can it be plain semver like1.2.3?

💡 Result:

In cargo-dist release manifests, announcement_tag is not guaranteed to include a v prefix and can be plain semver like 1.2.3. The field announcement_tag in DistManifest is an Option described as "The (git) tag associated with this announcement". Cargo-dist's tag parsing is permissive: it matches git tags using the regex ^[0-9]+.[0-9]+.[0-9]+*, which captures plain semver versions like 1.2.3 without a v prefix. Official docs and examples emphasize conventional formats like v1.0.0 or my-app-v1.0.0, but a key PR (#346) explicitly made parsing more flexible: "The 'v' prefix on a version is now optional". Supported formats include: - v1.0.0 - 1.0.0 - package-name-v1.0.0 - package-name-1.0.0 - package-name/v1.0.0 - package-name/1.0.0 Thus, plain semver like 1.2.3 is valid, and announcement_tag will reflect whatever git tag triggered the release or was passed via --tag.

Citations:


🏁 Script executed:

# First, find and read the Dockerfile to see the release URL construction
fd -t f "Dockerfile" | head -20

Repository: tower/tower-cli

Length of output: 69


🏁 Script executed:

# Check recent git tags to see what format the project uses
git tag -l | head -20

Repository: tower/tower-cli

Length of output: 41


🏁 Script executed:

# Read the Dockerfile to inspect the release URL construction
fd -t f "Dockerfile" -x cat -n

Repository: tower/tower-cli

Length of output: 851


🏁 Script executed:

cat -n .github/workflows/publish-docker.yml | sed -n '30,50p'

Repository: tower/tower-cli

Length of output: 739


🏁 Script executed:

git tag --list | tail -20

Repository: tower/tower-cli

Length of output: 41


Avoid v stripping/reconstruction mismatch for release asset lookup.

Line 35 strips v from TAG (the announcement_tag from cargo-dist), while the Dockerfile hardcodes v${VERSION} in the release download URL (Dockerfile line 11). Since cargo-dist allows plain semver tags like 1.2.3 (without v prefix), a release created with a non-prefixed tag would cause the Docker build to fail—it would request /download/v1.2.3/ when assets are actually at /download/1.2.3/.

Pass the original tag value to the Dockerfile so it uses the exact release tag format:

Suggested fix
-          VERSION="${TAG#v}"
+          VERSION="${TAG#v}"
+          RELEASE_TAG="$TAG"
           TAGS=(--tag "ghcr.io/tower/tower-cli:$VERSION")
@@
           docker buildx build \
             --platform linux/amd64,linux/arm64 \
             --build-arg VERSION="$VERSION" \
+            --build-arg RELEASE_TAG="$RELEASE_TAG" \
             "${TAGS[@]}" \
             --push .

In Dockerfile:

-ARG VERSION
+ARG VERSION
+ARG RELEASE_TAG
@@
-      "https://github.com/tower/tower-cli/releases/download/v${VERSION}/tower-${ARCH}-unknown-linux-musl.tar.xz" \
+      "https://github.com/tower/tower-cli/releases/download/${RELEASE_TAG}/tower-${ARCH}-unknown-linux-musl.tar.xz" \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-docker.yml around lines 35 - 43, The build strips
a leading "v" into VERSION but the Dockerfile expects the original tag
(v-prefixed or not) when constructing the release download URL, causing
mismatches; stop relying on stripped VERSION for the download and pass the
original TAG through to the Docker build. Modify the workflow to set
VERSION="${TAG#v}" as before for image tagging but also pass the unmodified TAG
as a separate build-arg (e.g., RELEASE_TAG or FULL_TAG) in the docker buildx
build invocation (alongside --build-arg VERSION="$VERSION"), and update the
Dockerfile to use that RELEASE_TAG when forming the /download/... URL (instead
of v${VERSION}) so the exact release tag format is preserved.

Comment thread Dockerfile
Comment on lines +16 to +18
FROM gcr.io/distroless/static-debian12
COPY --from=fetch /out/tower /usr/local/bin/tower
ENTRYPOINT ["/usr/local/bin/tower"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does gcr.io/distroless/static-debian12 run as root by default, and what is the recommended non-root UID/GID for distroless static images?

💡 Result:

Yes, gcr.io/distroless/static-debian12 (the latest tag) runs as root (UID 0) by default. The image has variants including :nonroot, which sets the default user to the nonroot user with UID/GID 65532. The recommended non-root UID/GID for distroless static images is 65532:65532, corresponding to the built-in nonroot user. This is the standard unprivileged user provided in all distroless images for running containers securely without root privileges. Users can also use nobody (UID/GID 65534), but nonroot is the specifically recommended one via the :nonroot tag. To use it, either pull gcr.io/distroless/static-debian12:nonroot or add USER nonroot (or USER 65532:65532) in your Dockerfile when basing on the root variant.

Citations:


Run the container as non-root.

Line 16 uses gcr.io/distroless/static-debian12, which defaults to running as root (UID 0). Lines 17–18 do not set a user, so the container will run with root privileges. Add a USER directive in the final stage to run as the recommended non-root user.

Suggested fix
 FROM gcr.io/distroless/static-debian12
 COPY --from=fetch /out/tower /usr/local/bin/tower
+USER 65532:65532
 ENTRYPOINT ["/usr/local/bin/tower"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM gcr.io/distroless/static-debian12
COPY --from=fetch /out/tower /usr/local/bin/tower
ENTRYPOINT ["/usr/local/bin/tower"]
FROM gcr.io/distroless/static-debian12
COPY --from=fetch /out/tower /usr/local/bin/tower
USER 65532:65532
ENTRYPOINT ["/usr/local/bin/tower"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 16 - 18, The final image stage uses
gcr.io/distroless/static-debian12 and sets ENTRYPOINT to /usr/local/bin/tower
but does not set a non-root user; update the final stage of the Dockerfile to
add a USER directive (e.g., a non-root UID/GID like 1000 or a created user) so
the container does not run as root, and ensure /usr/local/bin/tower and any
required directories have appropriate ownership/permissions for that UID before
switching users (adjust COPY/permission steps as needed).

@socksy socksy force-pushed the feature/add-dockerfile branch from 575fdd5 to fc4e07d Compare April 22, 2026 13:28
@socksy socksy merged commit 21e3b20 into develop Apr 22, 2026
51 of 54 checks passed
@socksy socksy deleted the feature/add-dockerfile branch April 22, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants